namespace NHCodeFirst.Helpers
{
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml.Linq;
    using Extensions;
    using HbmModel;

    internal class HbmGenerator
    {
        public static string GenerateHbm(HibernateMapping mapping)
        {
            var builder = new StringBuilder();
            builder.AppendLine(string.Format("<hibernate-mapping xmlns=\"{0}\" assembly=\"{1}\" namespace=\"{2}\">",
                                             "urn:nhibernate-mapping-2.2", mapping.Assembly, mapping.Namespace));
            builder.AppendLine(GenerateClass(mapping.Class).ToString());
            builder.AppendLine("</hibernate-mapping>");
            var text = builder.ToString();
            return XDocument.Parse(text).ToString();
        }

        private static XElement GenerateClass(Class @class)
        {
            var element = new XElement(@class.ElementType);
            element.AddAttribute("name", @class.Name);
            element.AddAttribute("abstract", @class.Abstract);
            element.AddAttribute("batch-size", @class.BatchSize);
            element.AddAttribute("catalog", @class.Catalog);
            element.AddAttribute("check", @class.Check);
            element.AddAttribute("discriminator-value", @class.DiscriminatorValue);
            element.AddAttribute("dynamic-insert", @class.DynamicInsert);
            element.AddAttribute("dynamic-update", @class.DynamicUpdate);
            element.AddAttribute("entity-name", @class.EntityName);
            element.AddAttribute("lazy", @class.Lazy);
            element.AddAttribute("mutable", @class.Mutable);
            element.AddAttribute("optimistic-lock", @class.OptimisticLock);
            element.AddAttribute("persister", @class.Persister);
            element.AddAttribute("polymorphism", @class.Polymorphism);
            element.AddAttribute("proxy", @class.Proxy);
            element.AddAttribute("rowid", @class.RowId);
            element.AddAttribute("schema", @class.Schema);
            element.AddAttribute("schema-action", @class.SchemaAction);
            element.AddAttribute("select-before-update", @class.SelectBeforeUpdate);
            element.AddAttribute("subselect", @class.SubSelect);
            element.AddAttribute("table", @class.Table);
            element.AddAttribute("where", @class.Where);
            element.AddAttribute("extends", @class.Extends);           
//            if (@class.CompositeId != null && @class.Id != null)
//                throw new MappingException("Using both Composite-Id and Id is not valid for mapping!");
            if (@class.Version != null && @class.TimeStamp != null)
                throw new MappingException("Using both TimeStamp and Version is not valid for mapping!");
//            if (@class.CompositeId != null)
//                element.Add(GenerateCompositeId(@class.CompositeId));
            if (@class.Id != null)
                element.Add(GenerateId(@class.Id));
//            if (@class.NaturalId != null)
//                element.Add(GenerateNaturalId(@class.NaturalId));
            if (@class.Discriminator != null)
                element.Add(GenerateDiscriminator(@class.Discriminator));
            if (@class.Version != null && @class.Usages.ContainsKey(@class.Version.Name) && @class.Usages[@class.Version.Name] == PropertyUsage.Version)
                element.Add(GenerateVersion(@class.Version));
            if (@class.TimeStamp != null && @class.Usages.ContainsKey(@class.TimeStamp.Name) && @class.Usages[@class.TimeStamp.Name] == PropertyUsage.TimeStamp)
                element.Add(GenerateTimeStamp(@class.TimeStamp));
            return element;
        }

        private static void GenerateMemberContainer(XElement rootElement, MemberContainer memberContainer)
        {
            foreach (var property in memberContainer.Properties.Where(pair => memberContainer.Usages.ContainsKey(pair.Key) && memberContainer.Usages[pair.Key] == PropertyUsage.Property))
                rootElement.Add(GenerateProperty(property.Value, memberContainer.Ignored.Contains(property.Key)));
            foreach (var manyToOne in memberContainer.ManyToOnes.Where(pair => memberContainer.Usages.ContainsKey(pair.Key) && memberContainer.Usages[pair.Key] == PropertyUsage.ManyToOne))
                rootElement.Add(GenerateManyToOne(manyToOne.Value));
            foreach (var oneToOne in memberContainer.OneToOnes.Where(pair => memberContainer.Usages.ContainsKey(pair.Key) && memberContainer.Usages[pair.Key] == PropertyUsage.OneToOne))
                rootElement.Add(GenerateOneToOne(oneToOne.Value));
            foreach (var component in memberContainer.Components.Where(pair => memberContainer.Usages.ContainsKey(pair.Key) && memberContainer.Usages[pair.Key] == PropertyUsage.Component))
                rootElement.Add(GenerateComponent(component.Value));
            foreach (var collection in memberContainer.Collections.Where(pair => memberContainer.Usages.ContainsKey(pair.Key) && memberContainer.Usages[pair.Key] == PropertyUsage.Collection))
                rootElement.Add(GenerateCollection(collection.Value));
            foreach (var any in memberContainer.Anies.Where(pair => memberContainer.Usages.ContainsKey(pair.Key) && memberContainer.Usages[pair.Key] == PropertyUsage.Any))
                rootElement.Add(GenerateAny(any.Value));
        }

        private static XElement GenerateTimeStamp(TimeStamp timeStamp)
        {
            var element = new XElement("timestamp");
            element.AddAttribute("access", timeStamp.Access);
            element.AddAttribute("generated", timeStamp.Generated);
            element.AddAttribute("name", timeStamp.Name);
            element.AddAttribute("source", timeStamp.Source);
            element.AddAttribute("unsaved-value", timeStamp.UnsavedValue);
            element.AddAttribute("column", timeStamp.Column);
            return element;
        }

//        private static XElement GenerateCompositeId(CompositeId compositeId)
//        {
//            var element = new XElement("composite-id");
//            element.AddAttribute("access", compositeId.Access);
//            element.AddAttribute("mapped",compositeId.Mapped);
//            element.AddAttribute("class", compositeId.Class);
//            element.AddAttribute("node", compositeId.Node);
//            element.AddAttribute("name", compositeId.Name);
//            element.AddAttribute("unsaved-value", compositeId.UnsavedValue);
//            if(compositeId.KeyManyToOnes != null)
//                foreach (var keyManyToOne in compositeId.KeyManyToOnes)
//                    element.Add(GenerateKeyManyToOne(keyManyToOne));
//            if(compositeId.KeyProperties != null)
//                foreach (var keyProperty in compositeId.KeyProperties)
//                    element.Add(GenerateKeyProperty(keyProperty));
//            return element;
//        }

        private static XElement GenerateId(Id id)
        {
            var element = new XElement("id");
            element.AddAttribute("access", id.Access);
            element.AddAttribute("name", id.Name);
            element.AddAttribute("unsaved-value", id.UnsavedValue);
            element.Add(GenerateColumn(id.Column));
            element.Add(GenerateType(id.Type));
            element.Add(GenerateIdGenerator(id.Generator));
            return element;
        }

//        private static XElement GenerateNaturalId(NaturalId naturalId)
//        {
//            var element = new XElement("natural-id");
//            element.AddAttribute("mutable", naturalId.Mutable);
//            if (naturalId.Properties != null)
//                foreach (var property in naturalId.Properties)
//                    element.Add(GenerateProperty(property));
//            if (naturalId.ManyToOnes != null)
//                foreach (var manyToOne in naturalId.ManyToOnes)
//                    element.Add(GenerateManyToOne(manyToOne));
//            if (naturalId.Components != null)
//                foreach (var component in naturalId.Components)
//                    element.Add(GenerateComponent(component));
//            return element;
//        }

        private static XElement GenerateKey(Key key)
        {
            var element = new XElement("key");
            element.AddAttribute("foreign-key", key.ForeignKey);
            element.AddAttribute("on-delete", key.OnDelete);
            element.AddAttribute("update", key.Update);
            foreach (var column in key.Columns)
                element.Add(GenerateColumn(column));
            return element;
        }

        private static XElement GenerateVersion(Version version)
        {
            var element = new XElement("version");
            element.AddAttribute("access", version.Access);
            element.AddAttribute("generated", version.Generated);
            element.AddAttribute("insert", version.Insert);
            element.AddAttribute("name", version.Name);
            element.AddAttribute("type", version.Type);
            element.AddAttribute("unsaved-value", version.UnsavedValue);
            if (version.Column != null)
                element.Add(GenerateColumn(version.Column));
            return element;
        }

        private static XElement GenerateDiscriminator(Discriminator discriminator)
        {
            var element = new XElement("discriminator");
            element.AddAttribute("force", discriminator.Force);
            element.AddAttribute("formula", discriminator.Formula);
            element.AddAttribute("insert", discriminator.Insert);
            element.AddAttribute("type", discriminator.Type);
            if (discriminator.Column != null)
                element.Add(GenerateColumn(discriminator.Column));
            return element;
        }

        private static XElement GenerateColumn(Column column)
        {
            var element = new XElement("column");
            element.AddAttribute("check", column.Check);
            element.AddAttribute("default", column.Default);
            element.AddAttribute("index", column.Index);
            element.AddAttribute("length", column.Length);
            element.AddAttribute("name", column.Name);
            element.AddAttribute("not-null", column.NotNull);
            element.AddAttribute("precision", column.Precision);
            element.AddAttribute("scale", column.Scale);
            element.AddAttribute("sql-type", column.SqlType);
            element.AddAttribute("unique", column.Unique);
            element.AddAttribute("unqiue-key", column.UniqueKey);
            return element;
        }

        private static XElement GenerateProperty(Property property, bool ignored)
        {
            if (ignored)
                return null;
            var element = new XElement("property");
            element.AddAttribute("access", property.Access);
            element.AddAttribute("formula", property.Formula);
            element.AddAttribute("generated", property.Generated);
            element.AddAttribute("insert", property.Insert);
            element.AddAttribute("lazy", property.Lazy);
            element.AddAttribute("name", property.Name);
            element.AddAttribute("optimistic-lock", property.OptimisticLock);
            element.AddAttribute("update", property.Update);
            if (property.Column != null)
                element.Add(GenerateColumn(property.Column));
            if (property.Type != null)
                element.Add(GenerateType(property.Type));
            return element;
        }

        private static XElement GenerateManyToOne(ManyToOne manyToOne)
        {
            var element = new XElement("many-to-one");
            element.AddAttribute("access", manyToOne.Access);
            element.AddAttribute("cascade", manyToOne.Cascade);
            element.AddAttribute("entity-name", manyToOne.EntityName);
            element.AddAttribute("foreign-key", manyToOne.ForeignKey);
            element.AddAttribute("fetch", manyToOne.Fetch);
            element.AddAttribute("formula", manyToOne.Formula);
            element.AddAttribute("insert", manyToOne.Insert);
            element.AddAttribute("lazy", manyToOne.Lazy);
            element.AddAttribute("name", manyToOne.Name);
            element.AddAttribute("not-found", manyToOne.NotFound);
            element.AddAttribute("optimistic-lock", manyToOne.OptimisticLock);
            element.AddAttribute("property-ref", manyToOne.PropertyRef);
            element.AddAttribute("update", manyToOne.Update);
            element.AddAttribute("outer-join", manyToOne.OuterJoin);
            element.AddAttribute("class", manyToOne.Class);
            foreach (var column in manyToOne.Columns)
                element.Add(GenerateColumn(column));
            return element;
        }

        private static XElement GenerateOneToOne(OneToOne oneToOne)
        {
            var element = new XElement("one-to-one");
            element.AddAttribute("name", oneToOne.Name);
            element.AddAttribute("access", oneToOne.Access);
            element.AddAttribute("cascade", oneToOne.Cascade);
            element.AddAttribute("class", oneToOne.Class);
            element.AddAttribute("entity-name",oneToOne.EntityName);
            element.AddAttribute("fetch", oneToOne.Fetch);
            element.AddAttribute("foreign-key", oneToOne.ForeignKey);
            element.AddAttribute("constrained", oneToOne.Constrained);
            element.AddAttribute("formula", oneToOne.Formula);
            element.AddAttribute("lazy", oneToOne.Lazy);
            element.AddAttribute("outer-join", oneToOne.OuterJoin);
            element.AddAttribute("property-ref", oneToOne.PropertyRef);
            return element;
        }

        private static XElement GenerateComponent(Component component)
        {
            var element = new XElement("component");
            element.AddAttribute("access", component.Access);
            element.AddAttribute("insert", component.Insert);
            element.AddAttribute("name", component.Name);
            element.AddAttribute("optimistic-lock", component.OptimisticLock);
            element.AddAttribute("unique", component.Unique);
            element.AddAttribute("update", component.Update);
            element.AddAttribute("class", component.Class);
            element.AddAttribute("lazy", component.Lazy);
            GenerateMemberContainer(element, component);
            return element;
        }

        private static XElement GenerateCollection(Collection collection)
        {
            var element = new XElement(collection.ElementType);
            element.AddAttribute("access", collection.Access);
            element.AddAttribute("batch-size", collection.BatchSize);
            element.AddAttribute("cascade", collection.Cascade);
            element.AddAttribute("catalog", collection.Catalog);
            element.AddAttribute("check", collection.Check);
            element.AddAttribute("collection-type", collection.CollectionType);
            element.AddAttribute("fetch", collection.Fetch);
            element.AddAttribute("generic", collection.Generic);
            element.AddAttribute("inverse", collection.Inverse);
            element.AddAttribute("lazy", collection.Lazy);
            element.AddAttribute("mutable", collection.Mutable);
            element.AddAttribute("name", collection.Name);
            element.AddAttribute("optimistic-lock", collection.OptimisticLock);
            element.AddAttribute("order-by", collection.OrderBy);
            element.AddAttribute("outer-join", collection.OuterJoin);
            element.AddAttribute("persister", collection.Persister);
            element.AddAttribute("schema", collection.Schema);
            element.AddAttribute("subselect",collection.SubSelect);
            element.AddAttribute("table", collection.Table);
            element.AddAttribute("where", collection.Where);
            if (collection.Key != null)
                element.Add(GenerateKey(collection.Key));
            if (collection.ManyToMany!=null)
            {
//                if (collection.ManyToMany.IndexManyToMany != null)
//                    element.Add(GenerateManyToManyIndex(collection.ManyToMany.IndexManyToMany));
                element.Add(GenerateManyToMany(collection.ManyToMany));
            }
            if (collection.OneToMany != null)
                element.Add(GenerateOneToMany(collection.OneToMany));
//            if (collection.Index != null)
//                element.Add(GenerateIndex(collection.Index));
            return element;
        }

        private static XElement GenerateIdGenerator(Generator generator)
        {
            var element = new XElement("generator");
            element.AddAttribute("class", generator.Class);
            foreach (var parameter in generator.Params)
                element.Add(GeneratorParam(parameter));
            return element;
        }

        private static XElement GenerateType(TypeE type)
        {
            var element = new XElement("type");
            if (type.Parameters != null)
                foreach (var parameter in type.Parameters)
                    element.Add(GeneratorParam(parameter));
            return element;
        }

        private static XElement GeneratorParam(KeyValuePair<string,object> parameter)
        {
            var element = new XElement("param");
            element.AddAttribute("name", parameter.Key);
            element.Value = parameter.Value.ToString();
            return element;
        }

//        private static XElement GenerateKeyManyToOne(KeyManyToOne keyManyToOne)
//        {
//            var element = new XElement("key-many-to-one");
//            element.AddAttribute("access", keyManyToOne.Access);
//            element.AddAttribute("class", keyManyToOne.Class);
//            element.AddAttribute("entity-name", keyManyToOne.EntityName);
//            element.AddAttribute("foreign-key", keyManyToOne.ForeignKey);
//            element.AddAttribute("lazy", keyManyToOne.Lazy);
//            element.AddAttribute("name", keyManyToOne.Name);
//            element.AddAttribute("not-found", keyManyToOne.NotFound);
//            element.Add(GenerateColumn(keyManyToOne.Column));
//            return element;
//        }
//
//        private static XElement GenerateKeyProperty(KeyProperty keyProperty)
//        {
//            var element = new XElement("key-property");
//            element.AddAttribute("access", keyProperty.Access);
//            element.AddAttribute("name", keyProperty.Name);
//            element.AddAttribute("node", keyProperty.Node);
//            if (keyProperty.Type != null)
//                element.Add(GenerateType(keyProperty.Type));
//            if (keyProperty.Column != null)
//                element.Add(GenerateColumn(keyProperty.Column));
//            return element;
//        }

//        private static XElement GenerateIndex(Index index)
//        {
//            var element = new XElement("index");
//            element.AddAttribute("type", index.Type);
//            if (index.Column != null)
//                element.Add(GenerateColumn(index.Column));
//            return element;
//        }
//
        private static XElement GenerateManyToMany(ManyToMany manyToMany)
        {
            var element = new XElement("many-to-many");
            element.AddAttribute("class", manyToMany.Class);
            element.AddAttribute("fetch", manyToMany.Fetch);
            element.AddAttribute("not-found", manyToMany.NotFound);
            foreach (var column in manyToMany.Columns)
                element.Add(GenerateColumn(column));
            return element;
        }

        private static XElement GenerateAny(Any any)
        {
            var element = new XElement("any");
            element.AddAttribute("id-type", any.IdType);
            element.AddAttribute("meta-type", any.MetaType);
            element.AddAttribute("name", any.Name);
            element.AddAttribute("access", any.Access);
            element.AddAttribute("insert", any.Insert);
            element.AddAttribute("update", any.Update);
            element.AddAttribute("cascade", any.Cascade);
            element.AddAttribute("optimistic-lock", any.OptimisticLock);
            element.AddAttribute("lazy", any.Lazy);
            foreach (var metaValue in any.MetaValues)
            {
                var metaElement = new XElement("meta-value");
                metaElement.AddAttribute("value", metaValue.Key);
                metaElement.AddAttribute("class", metaValue.Value);
                element.Add(metaElement);
            }
            if (any.EntityIdentifierColumn != null)
                element.Add(GenerateColumn(any.EntityIdentifierColumn));
            if (any.EntityTypeColumn != null)
                element.Add(GenerateColumn(any.EntityTypeColumn));
            return element;

        }
//
//        private static XElement GenerateManyToManyIndex(IndexManyToMany index)
//        {
//            var element = new XElement("index-many-to-many");
//            element.AddAttribute("class", index.Class);
//            element.AddAttribute("entity-name", index.EntityName);
//            element.AddAttribute("foreign-key", index.ForeignKey);
//            if (index.Column != null)
//                element.Add(index.Column);
//            return element;
//        }

        private static XElement GenerateOneToMany(OneToMany oneToMany)
        {
            var element = new XElement("one-to-many");
            element.AddAttribute("class", oneToMany.Class);
            element.AddAttribute("entity-name", oneToMany.EntityName);
            element.AddAttribute("not-found", oneToMany.NotFound);
            return element;
        }
    }
}